/* -*-c++-*- 
 * This source code is proprietary of ADIT
 * Copyright (C) 2013 Advanced Driver Information Technology Joint Venture GmbH
 * All rights reserved
 *
 * Author: Vadiraj Kaamsha <vadiraj.kaamsha@in.bosch.com>
 * Author: Rudolf Dederer <rudolf.dederer@de.bosch.com>
*/

#ifndef OSGBATCHEDTEXT_BATCHERBASE
#define OSGBATCHEDTEXT_BATCHERBASE 1

#include <osg/Drawable>
#include <osg/Quat>
#include <osg/BlendFunc>
#include <osgText/Font>
#include <osgBatchedText/GlyphInfoContainer>
#include <osgBatchedText/BatchDrawableBase>
#include <osgBatchedText/VecArray>
#include <osgBatchedText/Export>

namespace osgBatchedText {

/* a comparator has been added to the map to compare the objects rather than the addresses */
typedef std::pair< FontDescr, osg::ref_ptr<BatcherBase> > tBatcherBasePair;
typedef std::map < FontDescr, osg::ref_ptr<BatcherBase> > tBatcherBaseMap;

enum FontTextureType
{
   E_INNER_FONT_TEXTURE = 0,
   E_OUTER_FONT_TEXTURE = 1,
   E_COUNT_FONT_TEXTURE
};

class OSGBATCHEDTEXT_EXPORT GlyphTextureSimple : public osg::Texture2D
{
public:

    GlyphTextureSimple(BatcherBase* batch, FontTextureType textureType);

    virtual const char* className() const { return "GlyphTextureSimple"; }

    /** return -1 if *this < *rhs, 0 if *this==*rhs, 1 if *this>*rhs.*/
    //virtual int compare(const osg::StateAttribute& rhs) const;

    /** Set the margin around each glyph, to ensure that texture filtering doesn't bleed adjacent glyph's into each other.*/
    void setGlyphImageMargin(unsigned int margin) { _margin = margin; }
    unsigned int getGlyphImageMargin() const { return _margin; }

    virtual void apply(osg::State& state) const;

    /** Set whether to use a mutex to ensure ref() and unref() are thread safe.*/
    //virtual void setThreadSafeRefUnref(bool threadSafe);

    /** Resize any per context GLObject buffers to specified size. */
    //virtual void resizeGLObjectBuffers(unsigned int maxSize);

protected:

    virtual ~GlyphTextureSimple();

    // parameter used to compute the size and position of empty space
    // in the texture which could accommodate new glyphs.
    BatcherBase* _batcherBase;
    FontTextureType _textureType;
    int _margin;

};

class OSGBATCHEDTEXT_EXPORT BatchGeodeBase : public osg::Geode
{
public:
   /** constructor */
   BatchGeodeBase();
   BatchGeodeBase(int renderBin);
   /** copy constructor */
   BatchGeodeBase(const BatchGeodeBase& src, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY);

   void init(BatchDrawableBase::tShaderType shaderType, bool enableDepthTest = true);

   int getRenderBin() const { return _renderBin; }

   META_Node(types, BatchGeodeBase);

protected:
   virtual ~BatchGeodeBase() {}

   virtual void createShaderProgram(BatchDrawableBase::tShaderType shaderType);

   int _renderBin;
   osg::ref_ptr<osg::Program> _shaderProgram;

   class BatchUpdateCallBack : public osg::NodeCallback
   {
   public:
      void operator()(osg::Node* node, osg::NodeVisitor* nv);
   };

   // Shader code.
   static const char* _vertSourceStraightOrtho;
   static const char* _vertSourceStraightScreen;
   static const char* _vertSourceCurvedGroundFixed;
   static const char* _vertSourceCurvedScreen;
   static const char* _fragSource;

private:
   BatchGeodeBase& operator=(const BatchGeodeBase&) /* = delete */;
};


class OSGBATCHEDTEXT_EXPORT BatcherBase : public GlyphInfoContainerBase, public osg::Referenced
{
public:
    BatcherBase(const FontDescr& fontDescr = FontDescr(), bool useHalfFloat = false, bool outline = true, GLint maxTextureSize = 256, unsigned int maxGlyphs = 500u, bool mipmapActive = true);
    BatcherBase(const BatcherBase& text);

    void init();

    unsigned int getTextureWidth() const { return _textureSize._texture_width; }

    unsigned int getTextureHeight() const { return _textureSize._texture_height; }

    bool isMipmapActive() const { return _mipmapActive; }

    void setTextureObject(osg::Texture::TextureObject* textureObject, FontTextureType textureType) { _textureObject[textureType] = textureObject; }
    osg::Texture::TextureObject* getTextureObject(FontTextureType textureType) const { return _textureObject[textureType]; }

    void deleteTextures() const;

    const osg::View* getCurrentView() const { return _view; }

    enum ColorGradientMode
    {
        SOLID = 0, // a.k.a. ColorGradients off
        PER_CHARACTER,
        OVERALL
    };

    enum ShaderMode
    {
        FRAGMENT = 0,
        VERTEX
    };

    /** update uniforms for draw */
    virtual void updateUniforms(osg::State& state) const;

    virtual void addBatchElementContainer(const osg::ref_ptr<BatchElementContainer>& batchElementContainer, int renderBin = 0, const osg::View* view = NULL, bool update = true);

    /** 
    * Function checks if glyph for requested char-code or glyph-index is present in glyphInfoContainer or not.
    * If not present, it tries to load it from TTF file. If the cache is full,
    * older ones have to be deleted before new ones are loaded. If cache cannot
    * be freed, there an ERROR occurs. This API is not thread safe.
    */
    BatchEntry* findOrAddGlyph(bool isCharCode, const unsigned int charCodeOrGlyphIndex, bool incrementCounter = true) const;

    static int getDefaultRenderBin(BatchDrawableBase::tShaderType shaderType);

    void clearBatchElementVectorsToDraw();

    unsigned int getNumberOfBatchedElements();

    void setSortBasedOnDisplayOrder(bool value) { _sortBasedOnDisplayOrder = value; }

    bool getSortBasedOnDisplayOrder() const { return _sortBasedOnDisplayOrder; }

    BatchDrawableBase* getOrCreateBatchDrawable(int renderBin, BatchDrawableBase::tShaderType shaderType, bool enableDepthTest);

    static osg::ref_ptr<BatcherBase> getOrCreateBatcherBase(const osg::ref_ptr<TextConfigBase>& textConfigBase, tBatcherBaseMap& BatcherBaseMap);

    void initializeTexture(osg::State& state) const;

    void releaseDeletedTextures(osg::State& state) const;

    void releaseSpace(osg::State& state);

    const std::map< int, osg::ref_ptr<BatchDrawableBase> >& getRenderBinVsBatchDrawableMap() { return _renderBinVsBatchDrawable; }

    osg::ref_ptr<osg::StateSet> getFontStateSet() const { return _newFontStateSet; }

    virtual void resetTextureModified() const { _texture_modified = false; }

    osg::Drawable* getDrawable(int renderBin);

    bool allocateSpaceForGlyph(osgText::glyphEntry &glyph, osg::State& state) const;
    bool releaseSpaceForGlyph(osgText::glyphEntry &glyph, osg::State& state) const;
    void generateMipMap(osg::State& state) const;

    unsigned int getMaxRetries() const { return _max_retries; }

    bool getUseHalfFloat() const { return _useHalfFloat; }

protected:

    virtual ~BatcherBase();

    //debug function
    //used to dump the font texture to disk
    void dumpTextures(GLuint tex_id, unsigned int contextID) const;

    virtual void addForDeletion(BatchEntry* batchEntry) const;

    /**
     * Function calculates the translation vector for the string
     * The translation is the sum of the tile center and the offset to the text position
     * This sum is multiplied with the current MV mat
     */
    void calculateTransVec(const osg::Matrix& mvMat) const;

    void sortListForDrawing() const;

    struct textureCol
    {
        unsigned int            _x;
        unsigned int            _free_width;

        inline textureCol() : _x(0), _free_width(0) { }
        inline textureCol(unsigned int x, unsigned int free_width) : _x(x), _free_width(free_width) { }
    };
    typedef std::list<textureCol> Texturecols;

    struct textureRow
    {
       unsigned int             _y;
       unsigned int             _height;
       bool                     _fixed;
       Texturecols              _cols;        // contains free areas within a row (default: x=0, width = texture_width)

       inline textureRow(): _y(0), _height(0), _fixed(false) { }
       inline textureRow(unsigned int y, unsigned int height, bool fixed, Texturecols& cols): _y(y), _height(height), _fixed(fixed)
       {
          _cols = cols;
       }
    };

    struct TextureDimensions
    {
       unsigned int        _texture_width;
       unsigned int        _texture_height;
       float               _inv_texture_width;
       float               _inv_texture_height;

       inline TextureDimensions(): _texture_width(0), _texture_height(0), _inv_texture_width(0.0f), _inv_texture_height(0.0f) { }
    };

    /** map render bin versus the drawable to be drawn in this render bin */
    std::map< int, osg::ref_ptr<BatchDrawableBase> > _renderBinVsBatchDrawable;

    /** pointer to current view to compare in drawImplementation */
    const osg::View* _view;

    /* For inner and outer textures */
    osg::ref_ptr<GlyphTextureSimple> _fontTexture[E_COUNT_FONT_TEXTURE];

    osg::ref_ptr<osg::StateSet> _newFontStateSet;

    /** if set to true elements the drawing order is based on priority of elements, otherwise the elements are drawn regarding their z-distance */
    bool _sortBasedOnDisplayOrder;

private:
    BatcherBase& operator=(const BatcherBase&) /* = delete */;

#ifdef USE_COMBINED_SUBLOAD
    unsigned char*      _data;
    bool                _combined_subload;
#endif   //USE_COMBINED_SUBLOAD

    mutable bool        _texture_modified;

    unsigned int        _max_retries;
    unsigned int        _height_factor;

    mutable bool        _initailizeTexture;

    bool                _useHalfFloat;
    bool                _mipmapActive;

    /* For inner and outer textures */
    mutable osg::Texture::TextureObject* _textureObject[E_COUNT_FONT_TEXTURE];

    mutable std::vector<textureRow> _rows;
    mutable TextureDimensions       _textureSize;

    mutable std::list<BatchEntry*> _deleteFromTexture;
    //////////////////////////////////////////////////////////////////////////
};


}

#endif
